<?php

namespace Saber\Helper\Basis;


use ArrayAccess;
use InvalidArgumentException;


class Arr {


    /**
     *
     * Cross join the given arrays, returning all possible permutations.
     *
     * @param array ...$arrays
     *
     * @return array
     */
    public static function crossJoin (...$arrays): array {
        $results = [[]];
        //基础
        foreach ( $arrays as $index => $array ) {
            $append = [];

            foreach ( $results as $product ) {
                foreach ( $array as $item ) {
                    $product[$index] = $item;

                    $append[] = $product;
                }
            }

            $results = $append;
        }

        return $results;
    }

    /**
     * Divide an array into two arrays. One with keys and the other with values.
     *
     * @param array $array
     *
     * @return array
     */
    public static function divide ($array) {

        return [array_keys ($array) , array_values ($array)];
    }



    /**
     * Determine if the given key exists in the provided array.
     *
     * @param \ArrayAccess|array $array
     * @param string|int $key
     *
     * @return bool
     */
    public static function exists ($array , $key) {

        if ( $array instanceof ArrayAccess ) {
            return $array->offsetExists ($key);
        }

        return array_key_exists ($key , $array);
    }

    /**
     * Return the first element in an array passing a given truth test.
     *
     * @param array $array
     * @param callable|null $callback
     * @param mixed $default
     *
     * @return mixed
     */
    public static function first ($array , callable $callback = null , $default = null) {

        if ( is_null ($callback) ) {
            if ( empty($array) ) {
                return value ($default);
            }

            foreach ( $array as $item ) {
                return $item;
            }
        }

        foreach ( $array as $key => $value ) {
            if ( call_user_func ($callback , $value , $key) ) {
                return $value;
            }
        }

        return value ($default);
    }

    /**
     * Return the last element in an array passing a given truth test.
     *
     * @param array $array
     * @param callable|null $callback
     * @param mixed $default
     *
     * @return mixed
     */
    public static function last ($array , callable $callback = null , $default = null) {

        if ( is_null ($callback) ) {
            return empty($array) ? value ($default) : end ($array);
        }

        return static::first (array_reverse ($array , true) , $callback , $default);
    }



    /**
     * Determines if an array is associative.
     *
     * An array is "associative" if it doesn't have sequential numerical keys beginning with zero.
     *
     * @param array $array
     *
     * @return bool
     */
    public static function isAssoc (array $array) {

        $keys = array_keys ($array);

        return array_keys ($keys) !== $keys;
    }

    /**
     * Get a subset of the items from the given array.
     *
     * @param array $array
     * @param array|string $keys
     *
     * @return array
     */
    public static function only ($array , $keys) {

        return array_intersect_key ($array , array_flip ((array) $keys));
    }


    /**
     * Push an item onto the beginning of an array.
     *
     * @param array $array
     * @param mixed $value
     * @param mixed $key
     *
     * @return array
     */
    public static function prepend ($array , $value , $key = null) {

        if ( is_null ($key) ) {
            array_unshift ($array , $value);
        } else {
            $array = [$key => $value] + $array;
        }

        return $array;
    }

    /**
     * Get one or a specified number of random values from an array.
     *
     * @param array $array
     * @param int|null $number
     *
     * @return mixed
     *
     * @throws \InvalidArgumentException
     */
    public static function random ($array , $number = null) {

        $requested = is_null ($number) ? 1 : $number;

        $count = count ($array);

        if ( $requested > $count ) {
            throw new InvalidArgumentException("You requested {$requested} items, but there are only {$count} items available.");
        }

        if ( is_null ($number) ) {
            return $array[array_rand ($array)];
        }

        if ( (int) $number === 0 ) {
            return [];
        }

        $keys = array_rand ($array , $number);

        $results = [];

        foreach ( (array) $keys as $key ) {
            $results[] = $array[$key];
        }

        return $results;
    }


    /**
     * Shuffle the given array and return the result.
     *
     * @param array $array
     * @param int|null $seed
     *
     * @return array
     */
    public static function shuffle ($array , $seed = null) {

        if ( is_null ($seed) ) {
            shuffle ($array);
        } else {
            srand ($seed);

            usort ($array , function () {

                return rand (- 1 , 1);
            });
        }

        return $array;
    }


    /**
     * If the given value is not an array and not null, wrap it in one.
     *
     * @param mixed $value
     *
     * @return array
     */
    public static function wrap ($value) {

        if ( is_null ($value) ) {
            return [];
        }

        return is_array ($value) ? $value : [$value];
    }


    /**
     * Converts a flat array to a tree structure.
     *
     * This function takes a flat array and converts it into a tree structure based on the provided keys.
     * The 'pk' parameter is the primary key that uniquely identifies each item in the array.
     * The 'pkey' parameter is the parent key that links a child item to its parent item in the array.
     * The 'skey' parameter is the sub key that will be used to store the child items of each parent item in the resulting tree structure.
     *
     * @param array $array The flat array to convert.
     * @param string $pk The primary key that uniquely identifies each item in the array. Default is 'id'.
     * @param string $pkey The parent key that links a child item to its parent item in the array. Default is 'pid'.
     * @param string $children_key
     *
     * @return array Returns the array converted into a tree structure.
     */
    public  static function array2tree (
        array $array , string $pk = 'id' , string $pkey = 'pid' , string $children_key = 'children'): array {

        $list = [];
        $tree = [];
        foreach ( $array as $item ) {
            $list[$item[$pk]] = $item;
        }

        foreach ( $list as &$item ) {
            if ( isset($list[$item[$pkey]]) ) {
                $list[$item[$pkey]][$children_key][] = &$item;
            } else {
                $tree[] = &$item;
            }
        }

        return $tree;
    }

    /**
     * Converts a tree structure to a flat array.
     *
     * This function takes a tree structure and converts it into a flat array.
     * The 'children_key' parameter is the key that is used to store the child items of each parent item in the tree structure.
     *
     * @param array  $tree The tree structure to convert.
     * @param string $children_key
     *
     * @return array Returns the tree structure converted into a flat array.
     */
    public  static function tree2array(array $tree, string $children_key = 'children'): array {
        $array = [];

        foreach ($tree as $item) {
            $children = [];

            if (isset($item[$children_key])) {
                $children = $item[$children_key];
                unset($item[$children_key]);
            }

            $array[] = $item;
            if (!empty($children)) {
                $array = array_merge($array, tree2array($children, $children_key));
            }
        }

        return $array;
    }


    /**
     * 边际算法
     * Converts a tree structure to a flat array using margin algorithm.
     *
     * This function takes a tree structure and converts it into a flat array.
     * The 'children_key' parameter is the key that is used to store the child items of each parent item in the tree structure.
     * The 'depth' parameter is used to store the depth of each item in the tree structure.
     * The 'dimension' parameter is used to store the dimension of each item in the tree structure.
     * The 'counter' parameter is used to calculate the left and right values.
     * The 'index' parameter is used to give each node a unique, searchable index.
     *
     * @param array  $tree The tree structure to convert.
     * @param string $children_key
     * @param int    $depth
     * @param int    $counter
     * @param int    $index
     *
     * @return array Returns the tree structure converted into a flat array.
     */
    public  static function arrayMarginal(array $tree, string $children_key = 'children', int $depth = 0, int &$counter = 0, int &$index = 0): array {
        $array = [];
        foreach ($tree as $item) {
            $children = [];

            if (isset($item[$children_key])) {
                $children = $item[$children_key];
                unset($item[$children_key]);
            }

            $item['depth'] = $depth;
            $item['left'] = ++$counter;
            $item['index'] = ++$index;

            if (!empty($children)) {
                $array = array_merge($array, static::arrayMarginal($children, $children_key, $depth + 1,  $counter, $index));
            }
            $item['right'] = ++$counter;
            $array[] = $item;
        }

        return $array;
    }
}
